// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: donggu

#pragma once

#include <cmath>
#include <deque>
#include <stdexcept>
#include <utility>

namespace coin2::base::window {

struct Sample {
  int64_t timestamp;
  double value;
};

class TimeMovingWindow {
 public:
  explicit TimeMovingWindow(int64_t interval) : interval_(interval) {
  }

  size_t size() const { return window_.size(); }
  int64_t time_range() const {
    return size() < 2 ? 0 : window_.back().timestamp - window_.front().timestamp;
  }
  double sum() const { return sum_; }
  double mean() const {
    if (window_.empty()) {
      throw std::runtime_error("called mean() on empty window");
    } else {
      return sum_ / window_.size();
    }
  }

  void Push(int64_t timestamp, double value) {
    int64_t old = timestamp - interval_;
    while (!window_.empty() && window_.front().timestamp <= old) {
      sum_ -= window_.front().value;
      window_.pop_front();
    }
    sum_ += value;
    window_.push_back(Sample{timestamp, value});
  }

  void Clear() {
    sum_ = 0;
    window_.clear();
  }

 private:
  int64_t interval_ = 0;
  double sum_ = 0;
  std::deque<Sample> window_;
};

class SecondOrderTimeMovingWindow {
 public:
  explicit SecondOrderTimeMovingWindow(int64_t interval) : interval_(interval) {
  }

  void Push(int64_t timestamp, double value) {
    int64_t old = timestamp - interval_;
    while (!window_.empty() && window_.front().timestamp <= old) {
      sum_ -= window_.front().value;
      sum_sq_ -= window_.front().value * window_.front().value;
      window_.pop_front();
    }
    sum_ += value;
    sum_sq_ += value * value;
    window_.push_back(Sample{timestamp, value});
  }

  double sum() { return sum_; }
  double mean() {
    if (window_.empty()) {
      throw std::runtime_error("called mean() on empty window");
    } else {
      return sum_ / window_.size();
    }
  }

  double var() {
    if (window_.empty()) {
      throw std::runtime_error("called var() on empty window");
    } else {
      // E[X^2] - E[X]^2
      size_t n = window_.size();
      return (sum_sq_ - (sum_ * sum_ / n)) / n;
    }
  }

  double std() { return std::sqrt(var()); }

  void Clear() {
    sum_ = 0;
    sum_sq_ = 0;
    window_.clear();
  }

 private:
  int64_t interval_ = 0;
  double sum_ = 0;
  double sum_sq_ = 0;
  std::deque<Sample> window_;
};

}  // namespace coin2::base::window
